package com.maimao.kill.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.maimao.core.model.resp.Result;
import com.maimao.core.utils.DateUtils;
import com.maimao.core.utils.NumUtils;
import com.maimao.kill.client.GoodsClient;
import com.maimao.kill.service.KillApplyService;
import com.maimao.kill.service.RequirementService;
import com.maimao.kill.service.ScheduleService;
import com.maimao.model.goods.Comment;
import com.maimao.model.goods.dto.CommentAggDto;
import com.maimao.model.goods.enums.CommentType;
import com.maimao.model.kill.ApplySku;
import com.maimao.model.kill.KillApply;
import com.maimao.model.kill.KillRequirement;
import com.maimao.model.kill.Schedule;
import com.maimao.model.kill.enums.CheckTypeEnum;
import com.maimao.model.kill.enums.KillApplyFailReason;
import com.maimao.model.kill.enums.KillApplyStatusEnum;
import com.maimao.model.kill.input.ApplySkuNewInput;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.maimao.core.constant.Constants.COMMA;
import static com.maimao.core.constant.Constants.ZERO;
import static com.maimao.model.kill.enums.KillApplyFailReason.ACTIVITY_HAS_BEGIN;

/**
 * 自动审核秒杀商品报名申请
 *
 * @author MaoLin Wang
 * @date 2021/4/10 5:12 下午
 */
@Component
@Slf4j
public class CheckKillApplyJobHandler {

    public static final Integer DEFAULT_DAY_LIMIT = 5;

    public static final BigDecimal DEFAULT_DISCOUNT_MAX = BigDecimal.valueOf(9);

    public static final BigDecimal DEFAULT_DISCOUNT_MIN = BigDecimal.valueOf(1);

    public static final Integer DEFAULT_GOOD_RATE_MONTH_LIMIT = 1;

    public static final Integer DEFAULT_NEW_GOODS_DAY_LIMIT = 30;

    public static final Integer DEFAULT_SHOP_GOOD_RATE = 90;

    public static final Integer DEFAULT_STOCK = 300;
    public static final String REASON_PREFIX = "•";

    private static BigDecimal ZERO_DECIMAL = BigDecimal.valueOf(0);


    @Autowired
    private KillApplyService killApplyService;

    @Autowired
    private GoodsClient goodsClient;

    private KillApply queryParam;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private ScheduleService scheduleService;


    @PostConstruct
    public void initQueryParams() {
        KillApply build = KillApply.builder()
                .activityId(RequirementService.SEC_KILL_ACTIVITY_ID)
                .cancel(ZERO)
                .status(KillApplyStatusEnum.UN_CHECK.getCode())
                .build();
        queryParam = build;
    }

    @XxlJob("checkKillApplyJobHandler")
    public ReturnT checkKillApplyJobHandler() {
        KillRequirement killRequirement = RequirementService.REQUIREMENT_CACHE.get(RequirementService.SEC_KILL_ACTIVITY_ID);
        if (killRequirement == null) {
            killRequirement = KillRequirement.builder()
                    .activityId(RequirementService.SEC_KILL_ACTIVITY_ID)
                    .dayLimit(DEFAULT_DAY_LIMIT)
                    .discountMax(DEFAULT_DISCOUNT_MAX)
                    .discountMin(DEFAULT_DISCOUNT_MIN)
                    .goodRateMonthLimit(DEFAULT_GOOD_RATE_MONTH_LIMIT)
                    .newGoodsDayLimit(DEFAULT_NEW_GOODS_DAY_LIMIT)
                    .goodsGoodRate(DEFAULT_SHOP_GOOD_RATE)
                    .stock(DEFAULT_STOCK)
                    .build();
        }
        LocalDateTime now = DateUtils.now();
        KillApplyStatusEnum fail = KillApplyStatusEnum.FAIL;
        KillApplyStatusEnum ok = KillApplyStatusEnum.OK;
        KillApplyStatusEnum transfer = KillApplyStatusEnum.TRANSFER;
        Integer systemType = CheckTypeEnum.SYSTEM.getStatus();

        List<KillApply> rows = killApplyService.query(queryParam, 0, 200).getRows();
        if (ObjectUtil.isNotEmpty(rows)) {
            XxlJobHelper.log("查询到待审核的记录数：{}", rows.size());

            List<Long> scheduleIds = rows.stream().map(KillApply::getScheduleId).collect(Collectors.toList());
            List<Schedule> scheduleList = scheduleService.selectBatch(scheduleIds);

            Map<Long, Schedule> scheduleMap = null;

            //当前处理的申请记录对应的排期已经满足申请条件的申请个数
            Map<Long, Integer> scheduleCountMap = null;
            if (ObjectUtil.isNotEmpty(scheduleList)) {
                scheduleMap = scheduleList.stream().collect(Collectors.toMap(Schedule::getId, c -> c));
                scheduleCountMap = scheduleList.stream().collect(Collectors.toMap(Schedule::getId, c -> 0));
            }
            for (KillApply apply : rows) {
                StringBuilder sb = new StringBuilder();

                try {
                    apply.setCheckType(systemType);
                    //1.检查开始时间是否已经过了
                   /* if (apply.getScStartTime().compareTo(now) < 0) {
                        apply.setStatus(fail.getCode());
                        appendReason(sb, ACTIVITY_HAS_BEGIN.getMsg());
                    }*/
                    BigDecimal discount = new BigDecimal(apply.getDiscount());
                    //2.检查折扣是否符合标准
                    if (killRequirement.getDiscountMin() != null && killRequirement.getDiscountMax() != null
                            && killRequirement.getDiscountMin().compareTo(ZERO_DECIMAL) > 0 && killRequirement.getDiscountMax().compareTo(ZERO_DECIMAL) > 0) {
                        if (discount.compareTo(killRequirement.getDiscountMin()) < 0 || discount.compareTo(killRequirement.getDiscountMax()) > 0) {
                            apply.setStatus(fail.getCode());
                            appendReason(sb,
                                    formatReason(KillApplyFailReason.DISCOUNT_OUT_OF_RANGE.getMsg(),
                                            killRequirement.getDiscountMin(),
                                            killRequirement.getDiscountMax()));
                        }
                    }

                    List<ApplySku> applySkus = JSONUtil.toList(apply.getParams(), ApplySku.class);

                    //3.检查库存是否达标
                    boolean skuExist = checkStock(killRequirement, fail, apply, sb, applySkus);


                    //4.检查是否在期限内报名过该活动
                    if (killRequirement.getDayLimit() != null && killRequirement.getDayLimit() > 0) {
                        List<KillApply> applied = killApplyService.selectApplied(apply.getActivityId(), apply.getGoodsId(), apply.getScStartTime(), killRequirement.getDayLimit());
                        if (ObjectUtil.isNotEmpty(applied)) {
                            apply.setStatus(fail.getCode());
                            appendReason(sb, formatReason(KillApplyFailReason.DAY_LIMIT_EXIST.getMsg(), killRequirement.getDayLimit()));
                        }
                    }

                    //5.检查是否存在新上架的sku
                    if (skuExist && killRequirement.getNewGoodsDayLimit() != null && killRequirement.getNewGoodsDayLimit() > 0) {
                        List<Long> skuIds = applySkus.stream().map(ApplySku::getId).collect(Collectors.toList());
                        //5.检查是否存在新上架sku
                        LocalDateTime fromTime = apply.getScStartTime().plusDays(killRequirement.getNewGoodsDayLimit());

                        Result<ApplySkuNewInput> skuIsNew = goodsClient.checkSkuIsNew(ApplySkuNewInput.builder().skuIds(skuIds).fromTime(fromTime).build());
                        if (skuIsNew != null && skuIsNew.getData() != null && skuIsNew.isFlag()) {
                            ApplySkuNewInput res = skuIsNew.getData();
                            if (ObjectUtil.isNotEmpty(res.getNotValidIds())) {
                                apply.setStatus(fail.getCode());
                                appendReason(sb, KillApplyFailReason.EXIST_NOT_VALID_SKU.getMsg());
                            }
                            if (ObjectUtil.isNotEmpty(res.getSkuIds())) {
                                //新上架的sku
                                apply.setStatus(fail.getCode());
                                appendReason(sb, formatReason(KillApplyFailReason.SKU_NEW_ON_SELL.getMsg(), StrUtil.join(COMMA, res.getSkuIds()), killRequirement.getNewGoodsDayLimit()));
                            }
                        } else {
                            XxlJobHelper.log("校验sku是否为新上架时出错,reason:{}，skuIds:{}", skuIsNew == null ? "-" : skuIsNew.getMessage(), skuIds);
                            apply.setStatus(transfer.getCode());
                            appendReason(sb, KillApplyFailReason.CHECK_ERROR.getMsg());
                        }
                    }

                    //6.检查商品好评率
                    if (!NumUtils.anyNullOrLteZero(killRequirement.getGoodsGoodRate())) {
                        checkGoodRate(killRequirement, fail, apply, sb);
                    }

                    //7.检查是否有剩余坑位
                    Schedule schedule = scheduleMap.get(apply.getScheduleId());
                    if (null == schedule) {
                        apply.setStatus(fail.getCode());
                        appendReason(sb, KillApplyFailReason.SCHEDULE_INVALID.getMsg());
                    } else {
                        Integer maxCount = schedule.getMaxCount();
                        Integer cont = scheduleCountMap.get(apply.getScheduleId());
                        if (cont < maxCount) {
                            scheduleCountMap.put(apply.getScheduleId(), ++cont);
                        } else {
                            //当前排期不足以分配当前申请
                            apply.setStatus(fail.getCode());
                            appendReason(sb, KillApplyFailReason.SCHEDULE_NOT_ENOUGH.getMsg());
                        }

                    }

                } catch (Exception e) {
                    XxlJobHelper.log("校验出错,reason:{},KillApply:{}", e.getMessage(), apply);
                    log.error("校验出错,reason:{},KillApply:{}", e, apply);
                    apply.setStatus(transfer.getCode());
                    appendReason(sb, KillApplyFailReason.CHECK_ERROR.getMsg());
                }
                String reply = sb.toString();
                if (StrUtil.isEmpty(reply)) {
                    apply.setStatus(ok.getCode());
                } else {
                    apply.setReply(sb.toString());
                }
            }

            XxlJobHelper.log("审核完毕，审核结果：{}", rows);
            log.info("审核完毕，审核结果：{}", rows);
            killApplyService.checkApply(rows);
        } else {
            XxlJobHelper.log("未查询到需要审核的记录");

        }
        return ReturnT.SUCCESS;
    }

    /**
     * 检查商品好评率
     */
    private void checkGoodRate(KillRequirement killRequirement, KillApplyStatusEnum fail, KillApply apply, StringBuilder sb) {
        Integer monthLimit = killRequirement.getGoodRateMonthLimit();
        Criteria criteria = Criteria.where("shopId").is(apply.getShopId()).and("goods_id").is(apply.getGoodsId());
        if (!NumUtils.nullOrLteZero(monthLimit)) {
            criteria.and("create_time").gte(LocalDateTime.now().plusMonths(-monthLimit)).and("pid").is(0);
        }
        Aggregation agg = Aggregation.newAggregation(
                Aggregation.match(criteria), Aggregation.group("type").count().as("count")
        );
        AggregationResults<CommentAggDto> aggRes = mongoTemplate.aggregate(agg, Comment.class, CommentAggDto.class);
        List<CommentAggDto> aggDtoList = aggRes.getMappedResults();
        if (ObjectUtil.isNotEmpty(aggDtoList)) {
            int goodCont = 0;
            int total = 0;
            for (CommentAggDto dto : aggDtoList) {
                if (dto.getId().equals(CommentType.GOOD.getType())) {
                    goodCont++;
                    total++;
                } else if (!dto.getId().equals(CommentType.REPLY.getType())) {
                    total++;
                }
            }
            if (total > 0) {
                BigDecimal divide = BigDecimal.valueOf(goodCont).divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP);
                double rate = divide.doubleValue() * 100;
                //好评率太低，则不允许
                if (rate < killRequirement.getGoodsGoodRate()) {
                    apply.setStatus(fail.getCode());
                    appendReason(sb, formatReason(KillApplyFailReason.GOOD_RATE_TOO_LOW.getMsg(), apply.getGoodsId(), killRequirement.getGoodsGoodRate() + "%"));
                }
            } else {
                apply.setStatus(fail.getCode());
                appendReason(sb, formatReason(KillApplyFailReason.GOOD_RATE_TOO_LOW.getMsg(), apply.getGoodsId(), killRequirement.getGoodsGoodRate() + "%"));
            }
        } else {
            apply.setStatus(fail.getCode());
            appendReason(sb, formatReason(KillApplyFailReason.GOOD_RATE_TOO_LOW.getMsg(), apply.getGoodsId(), killRequirement.getGoodsGoodRate() + "%"));
        }
    }


    /**
     * 检查库存是否达标
     *
     * @return 是否存在sku
     */
    private boolean checkStock(KillRequirement killRequirement, KillApplyStatusEnum fail, KillApply apply, StringBuilder sb, List<ApplySku> applySkus) {
        if (ObjectUtil.isNotEmpty(applySkus)) {
            if (killRequirement.getStock() == null || killRequirement.getStock() <= 0) {
                return true;
            }
            int totalStock = applySkus.stream().filter(applySku -> (applySku.getStock() != null && applySku.getStock() > 0))
                    .mapToInt(ApplySku::getStock).sum();
            if (totalStock < killRequirement.getStock()) {
                apply.setStatus(fail.getCode());
                appendReason(sb,
                        formatReason(KillApplyFailReason.STOCK_NOT_ENOUGH.getMsg(), killRequirement.getStock()));
            }
            return true;
        } else {
            apply.setStatus(fail.getCode());
            appendReason(sb, KillApplyFailReason.SKU_NOT_SELECTED.getMsg());
            return false;
        }
    }

    private void appendReason(StringBuilder sb, String msg) {
        sb.append(REASON_PREFIX).append(msg).append("\n");

    }

    private String formatReason(String msg, Object... params) {
        return String.format(msg, params);
    }

    public static void main(String[] args) {
        List<KillApply> killApplies = new LinkedList<>();
        killApplies.add(new KillApply().setId(1L));
        killApplies.add(new KillApply().setId(2L));
        killApplies.add(new KillApply().setId(3L));


    }


}
